package cn.spike.rockcc.service.impl;

import cn.spike.rockcc.domain.MessageSms;
import cn.spike.rockcc.domain.MessageSmsBlock;
import cn.spike.rockcc.errorenum.GlobalExceptionCode;
import cn.spike.rockcc.exception.BusinessException;
import cn.spike.rockcc.feignclient.UserFeignClient;
import cn.spike.rockcc.mapper.MessageSmsBlockMapper;
import cn.spike.rockcc.mapper.MessageSmsMapper;
import cn.spike.rockcc.result.JSONResult;
import cn.spike.rockcc.service.IMessageSmsService;
import cn.spike.rockcc.service.VerifyCodeService;
import cn.spike.rockcc.util.AssertUtil;
import cn.spike.rockcc.util.StrUtils;
import cn.spike.rockcc.util.VerifyCodeUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static cn.spike.rockcc.errorenum.GlobalExceptionCode.*;

/**
 * @author:spike
 * @create:2023-03-08 18:43
 */
@Service
@Slf4j
public class VerifyCodeServiceImpl implements VerifyCodeService {

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private MessageSmsMapper messageSmsMapper;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private HttpServletRequest httpServletRequest;

    @Autowired
    private MessageSmsBlockMapper blockMapper;

    @Override
    public JSONResult imageCodeKey(String imageCodeKey) {
        String code = StrUtils.getComplexRandomString(4);
        redisTemplate.opsForValue().set(imageCodeKey, code, 3, TimeUnit.MINUTES);
        String verifyCode = VerifyCodeUtils.verifyCode(115, 40, code);
        return JSONResult.success(verifyCode, "200");
    }

    /**
     * 发送手机短信的业务
     *
     * @param map
     */
    @Override
    @Transactional
    public JSONResult sendSmsCode(Map<String, String> map) {
        String mobile = map.get("mobile");
        String imageCode = map.get("imageCode");
        String imageCodeKey = map.get("imageCodeKey");
        //图形验证码的校验
        checkImageCode(mobile, imageCode, imageCodeKey);
        //校验ip是否为黑名单
        String ip = httpServletRequest.getRemoteAddr();
        Object o = redisTemplate.opsForValue().get("register:" + ip);
        if(!checkIp(ip,o))return JSONResult.error(GlobalExceptionCode.IP_BLOCK_EXCEPTION);
        //短信验证码校验及发送
        String phoneCode = checkSmsCode(mobile);
        //发送成功后将该ip存入redis并设置过期时间 - 目的：防止同一个ip在规则之上重复获取短信
        ipincrement(ip,o);
        //创建MessageSms对象并添加至数据库
        creatMessageSms(mobile, phoneCode, ip);
        return JSONResult.success();
    }

    /**
     * 校验ip是否为黑名单
     * @param ip
     */
    private Boolean checkIp(String ip,Object o) {
        if (o != null) {
            //五分钟内超三次
            if(Integer.parseInt(o.toString()) == 3){
                //加至黑名单
                blockMapper.insert(new MessageSmsBlock(new Date(),ip));
                //将ip的value设置为4 - 表示违规操作已添加至数据库 - 并设置60分钟封禁时间
                redisTemplate.opsForValue().set("register:" + ip,4,60,TimeUnit.MINUTES);
                return false;
            }
            //如果等于4 - 表示在黑名单内而且并没有到达解封时间
            if (Integer.parseInt(o.toString()) == 4)return false;
        }
        return true;
    }

    /**
     * 发送成功后将该ip存入redis并设置过期时间
     * @param ip
     */
    private void ipincrement(String ip,Object o) {
        //如果是null - 说明第一次获取短信
        if (o == null)redisTemplate.opsForValue().set("register:" + ip, 1, 5, TimeUnit.MINUTES);
        //如果不为null - 自增一次
        else redisTemplate.opsForValue().increment("register:" + ip);
    }


    /**
     * 短信验证码校验及发送
     * @param mobile
     * @return
     */
    private String checkSmsCode(String mobile) {
        Object o = redisTemplate.opsForValue().get("register:" + mobile);
        String phoneCode = null;
        //短信验证码生成及违规操作的判断
        if (o != null) {
            String oldPhoneCode = o.toString();
            long preTime = Long.parseLong(oldPhoneCode.split(":")[1]);
            if (preTime - System.currentTimeMillis() < 60000) throw new BusinessException(PHONECODE_REGET_EXCEPTION);
            phoneCode = oldPhoneCode.split(":")[0];
        } else {
            phoneCode = StrUtils.getRandomString(4);
        }
        //保存至redis刷新过期时间
        redisTemplate.opsForValue().set("register:" + mobile,
                phoneCode + ":" + System.currentTimeMillis(), 10, TimeUnit.MINUTES);
        //发送短信
        //SmsUtils.sendSms(mobile,"短信验证码为:"+phoneCode+"【请在10分钟内使用】");
        log.info(mobile + "-短信验证码为:" + phoneCode + "【请在10分钟内使用】");

        return phoneCode;
    }

    /**
     * 图形验证码的校验
     * @param mobile
     * @param imageCode
     * @param imageCodeKey
     */
    private void checkImageCode(String mobile, String imageCode, String imageCodeKey) {
        //判断注册信息是否为空
        if (StringUtils.isEmpty(mobile) || StringUtils.isEmpty(imageCode))
            throw new BusinessException(REGISTER_NULL_EXCEPTION);
        //调用微服务 判断改手机号是否已注册
        JSONResult jsonResult = userFeignClient.loadByPhone(mobile);
        if (jsonResult.getCode().equals("1000") && jsonResult.getData() != null)
            throw new BusinessException(REGISTER_RE_EXCEPTION);

        Object oldImageCode = redisTemplate.opsForValue().get(imageCodeKey);
        //判断输入的图形验证码是否为空
        if (oldImageCode == null) throw new BusinessException(IMAGECODE_TIMEOUT_EXCEPTION);
        //判断输入的验证码是否正确
        if (!imageCode.equalsIgnoreCase(oldImageCode.toString()))
            throw new BusinessException(IMAGECODE_FALSE_EXCEPTION);
    }

    /**
     * 创建MessageSms对象并添加至数据库
     * @param mobile
     * @param phoneCode
     */
    private void creatMessageSms(String mobile, String phoneCode, String ip) {
        //保存至MessageSms数据库
        MessageSms sms = new MessageSms();
        sms.setContent(mobile + "-短信验证码为:" + phoneCode + "【请在10分钟内使用】");
        sms.setPhone(mobile);
        sms.setSendTime(new Date());
        sms.setTitle("注册验证码");
        //获取当前请求
        //获取ip
        //ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //String ip = requestAttributes.getRequest().getRemoteAddr();
        sms.setIp(ip);
        messageSmsMapper.insert(sms);
    }
}
